
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <sys/time.h>

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#define MAXLINE 1500

int max(int a, int b)
{
    if(a > b)
    {
        return a;
    }
    return b;
}

void print_error_msg(int error_code, const char * msg)
{
    printf("error_code is : [%d] msg : [%s]\n", error_code, msg);
}

const char * gf_time()
{
    struct timeval tm;
    bzero(&tm, sizeof(tm));
    gettimeofday(&tm,NULL);

    return ctime(&tm.tv_sec);
}

void str_cli(FILE * fp, int sockfd)
{
    int maxfdp1, val, stdineof;

    ssize_t n, nwritetten;

    fd_set rset,    wset;
    // to 容纳从标准输入到套接字的数据缓冲区
    // frame 容纳从套接字到标准输出的数据的缓冲区
    char    to[MAXLINE],    frame[MAXLINE];

    // to_input_ptr指向标准输入的尾部
    // to_output_ptr指向要标准输入的头部（不包含已经发送的数据）
    /**
     *                            to_output_ptr       to_input_ptr
     *          to:[ 已经发出的数据 | 要发往服务器的数据 | 用于从标准输入读取数据的空闲空间]&to[MAXLINE]
     * 
     *                          frame_output_ptr       frame_input_ptr
     *          frame:[已经发出的数据 | 要发往标准输出的数据 | 用于从套接字读取的数据的空闲空间]&frame[MAXLINE]
     * 
     * 
     **/

    char * to_input_ptr,    * to_output_ptr, * frame_input_ptr, * frame_output_ptr;

    int val = 0;
    
    val = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, O_NONBLOCK | val);

    // stdin fd
    val = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK | val);

    // stdout fd
    val = fcntl(STDOUT_FILENO, F_GETFL, 0);
    fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);

    to_input_ptr    = to_output_ptr    = to;    // pointer to to buffer begin;
    frame_input_ptr = frame_output_ptr = frame; // pointer to frame buffer begin;

    // 输入状态
    int stdineof = 0;

    // 获取到最大的描述符
    maxfdp1 = max(max(STDOUT_FILENO,STDIN_FILENO), sockfd) + 1;

    for(; ; )
    {
        // 设置fd_set状态
        FD_ZERO(&rset);
        FD_ZERO(&wset);

        if(stdineof == 0 && to_input_ptr < &to[MAXLINE])
        {
            // 输入还没有结束且to buffer还有空间
            FD_SET(STDIN_FILENO, &rset);
        }

        if(frame_input_ptr < &frame[MAXLINE])
        {
            // read from socket （frame）
            FD_SET(sockfd, &rset);
        }

        if(to_output_ptr != to_input_ptr)
        {
            // data to write to socket
            FD_SET(sockfd, &wset);
        }

        if(frame_output_ptr != frame_input_ptr)
        {
            // data to write to stdout
            FD_SET(STDOUT_FILENO, &wset);
        }
        // block wait
        select(maxfdp1, &rset, &wset, NULL, NULL);

        if(FD_ISSET(STDIN_FILENO, &rset))
        {

            // 可以从标准输入读取数据
            if((n == read(STDIN_FILENO, to_input_ptr, &to[MAXLINE] - to_input_ptr)) < 0)
            {
                if(errno != EWOULDBLOCK)
                {
                    print_error_msg(errno,"read error from stdin");
                    break;
                }
                else if(n == 0)
                {
                    fprintf(stderr, "%s: EOF on stdin\n", gf_time());
                    // 结束标准输入
                    stdineof = 1;
                    if(to_output_ptr == to_input_ptr)
                    {
                        // 关闭sockfd描述符写的功能
                        shutdown(sockfd, SHUT_WR);
                    }
                }
                else
                {
                    fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(), n);
                    to_input_ptr += n;

                    FD_SET(sockfd, &wset);
                }
            }
        }
        if(FD_ISSET(sockfd, &rset))
        {
            if( (n = read(sockfd, frame_input_ptr, &frame[MAXLINE] - frame_input_ptr)) < 0)
            {
                if(errno != EWOULDBLOCK)
                {
                    print_error_msg(errno, "read error from sockfd");
                    break;
                }
            }
            else if(n == 0)
            {
                fprintf(stderr, "%s: EOF on socket\n",gf_time());
                if(stdineof)
                {
                    return;
                }
                else
                {
                    print_error_msg(0,"str cli: server terimnated prematurely");
                }
            }
            else
            {
                fprintf(stderr, "%s: read %d bytes from socket\n", gf_time(),n);
                frame_input_ptr += n;
                // 
                FD_SET(STDOUT_FILENO,&wset);
            }
        }
        
    }
}